/*--- unrle1.c ---------------------------- Listing 9-2 --------
 *  Uncompresses rle1 files
 *
 *-------------------------------------------------------------*/

#include <stdio.h>
#include <stdlib.h>

#define BUFFER_SIZE 30000

FILE *infile,
     *outfile;

int main ( int argc, char *argv[] )
{
    unsigned char *buffer,
         sentinel;
    int  bytes_read,
         count,
         i;

    /*--- process files open ---*/

    if ( argc != 3 )
    {
        fprintf ( stderr, "Uncompresses RLE1-compressed file\n"
                          "Usage: unrle1 infile outfile\n" );
        return ( EXIT_FAILURE );
    }

    if (( infile = fopen ( argv[1], "rb" )) == NULL )
    {
        fprintf ( stderr, "Error opening %s\n", argv[1] );
        return ( EXIT_FAILURE );
    }

    if (( outfile = fopen ( argv[2], "wb" )) == NULL )
    {
        fprintf ( stderr, "Error opening %s\n", argv[2] );
        return ( EXIT_FAILURE );
    }

    /*--- allocate input buffer ---*/

    buffer = (unsigned char *) malloc ( BUFFER_SIZE );
    if ( buffer == NULL )
    {
        fprintf ( stderr, "Unable to allocate %d bytes\n",
                    BUFFER_SIZE );
        return ( EXIT_FAILURE );
    }

    /*--- check the file header ---*/

    bytes_read = fread ( buffer, 1, 4, infile );
    if ( bytes_read != 4 )
    {
        fprintf ( stderr, "Unable to read %s\n", argv[1] );
        return ( EXIT_FAILURE );
    }

    if ( buffer[0] == 'R' && buffer[1] == 'L' &&
         buffer[2] == '1' )
            sentinel = buffer[3];
    else
    {
        fprintf ( stderr, "%s is not an RLE 1 file\n", argv[1] );
        return ( EXIT_FAILURE );
    }

    /*--- process the file ---*/

    while ( 1 )     /* loop until break occurs */
    {
        bytes_read = fread ( buffer, 1, BUFFER_SIZE, infile );
        if ( bytes_read == 0 )
            break;

        for ( i = 0; i < bytes_read; i++ )
        {
            if ( buffer[i] != sentinel )
                fputc ( buffer[i], outfile );
            else
            {       /* process sentinel */

                if ( i > bytes_read - 3 ) /* near buffer end */
                {
                    if ( i > bytes_read - 2 ) /* no bytes left */
                    {
                        bytes_read = fread ( buffer, 1,
                                    BUFFER_SIZE, infile );
                        if ( bytes_read < 2 )
                        {
                            fprintf ( stderr,
                                      "error in %s\n", argv[1] );
                            fclose ( infile );
                            unlink ( argv[2] );
                            return ( EXIT_FAILURE );
                        }
                        else
                        {
                            i = -1;
                            goto process_count;
                        }
                    }
                    else                      /* one byte left */
                    {
                        if ( buffer[i + 1] < 3 )
                            goto process_count;

                        count = buffer[i + 1];

                        bytes_read = fread ( buffer, 1,
                                    BUFFER_SIZE, infile );
                        if ( bytes_read < 1 )
                        {
                            fprintf ( stderr,
                                      "error in %s\n", argv[1] );
                            fclose ( infile );
                            unlink ( argv[2] );
                            return ( EXIT_FAILURE );
                        }

                        i = 0;
                        do
                        {
                            fputc ( buffer[i], outfile );
                        }
                            while ( --count );
                    }

                }
                else                    /* not end of buffer */
                {
                  process_count:

                    count = buffer[++i];

                    switch ( count )    /* what's the count? */
                    {
                        case 0:
                            fprintf ( stderr,
                                      "error in %s\n", argv[1] );
                            fclose ( infile );
                            unlink ( argv[2] );
                            return ( EXIT_FAILURE );
                        case 1:
                            fputc ( sentinel, outfile );
                            break;
                        case 2:
                            fputc ( sentinel, outfile );
                            fputc ( sentinel, outfile );
                            break;
                        default:
                            i += 1;
                            do
                            {
                                fputc ( buffer[i], outfile );
                            }
                                while ( --count );
                            break;
                    }
                }
            }
        }

        if ( bytes_read < BUFFER_SIZE )
            break;
    }

    fclose ( infile );
    fclose ( outfile );

    return ( EXIT_SUCCESS );
}
